/*                                                                         
 * Copyright 2010-2013 the original author or authors.                     
 *                                                                         
 * Licensed under the Apache License, Version 2.0 (the "License");         
 * you may not use this file except in compliance with the License.        
 * You may obtain a copy of the License at                                 
 *                                                                         
 *      http://www.apache.org/licenses/LICENSE-2.0                         
 *                                                                         
 * Unless required by applicable law or agreed to in writing, software     
 * distributed under the License is distributed on an "AS IS" BASIS,       
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and     
 * limitations under the License.                                          
 */
package com.shopng.utils;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;

import com.shopng.geode.api.JsonObject;

import au.com.bytecode.opencsv.CSVReader;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class Util
{
  
  protected static MessageDigest messageDigest = null;
  
  static
  {
    try
    {
      messageDigest = MessageDigest.getInstance("MD5");
    }
    catch (NoSuchAlgorithmException e)
    {
      LogUtil.getCoreLog().error("MD 5FileUtil messageDigest init failure", e);
    }
  }
  
  public final static synchronized String makeMD5String(byte[] bytes)
  {
    
    byte[] md5hash = messageDigest.digest(bytes);
    String md5Str = Util.encodeToBASE64(md5hash);
    return md5Str;
  }
  
  public final static URL makeFullURL(String config_server, String suffix)
  {
    URL url = null;
    if (config_server.endsWith("/"))
      config_server = config_server.substring(0, config_server.length() - 1);
    try
    {
      url = new URL(config_server + suffix);
    }
    catch (MalformedURLException e)
    {
      LogUtil.getCoreLog().error(suffix, e);
    }
    return url;
  }
  
  public final static URL strToURL(String str)
  {
    URL url = null;
    try
    {
      if (!(str.startsWith("file:") || str.startsWith("http:")))
        str = "file:/" + str;
      if (str.startsWith("file:") && !str.endsWith("/"))
        str += "/";
      url = new URL(str);
    }
    catch (Exception e)
    {
    }
    return url;
  }
  
  /***
   * Lgemlite/core/util/Util;
   * 
   * @param cls
   * @return
   */
  public final static String getInternalDesc(Class<?> cls)
  {
    String name = cls.getName();
    StringBuilder builder = new StringBuilder();
    builder.append("L");
    builder.append(name.replaceAll("\\.", "\\/"));
    builder.append(";");
    return builder.toString();
  }
  
  /**
   * gemlite/core/util/Util
   * 
   * @param cls
   * @return
   */
  public final static String getInternalName(Class<?> cls)
  {
    String name = cls.getName();
    StringBuilder builder = new StringBuilder();
    builder.append(name.replaceAll("\\.", "\\/"));
    return builder.toString();
  }
  
  public final static String getCallingClassName()
  {
    StackTraceElement[] elems= new Exception().getStackTrace();
    StringBuilder bd=new StringBuilder();
    for(StackTraceElement elem:elems)
    {
      bd.append(elem.getClassName()).append(".").append(elem.getMethodName()).append(" / ");
    }
    return bd.toString();
  }
  
  public final static String getCallingMethodName()
  {
    // LocationInfo info = new LocationInfo(new Throwable(),"gemlite");
    return "method";// info.getMethodName();
  }
  
  /**
   * Get current PID.
   * 
   * @return
   */
  public final static long getPID()
  {
    String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
    return Long.parseLong(processName.split("@")[0]);
  }
  
  public final static String printProperties()
  {
    StringBuilder bu = new StringBuilder();
    Properties props = System.getProperties();
    Iterator<Entry<Object, Object>> it = props.entrySet().iterator();
    while (it.hasNext())
    {
      Entry<Object, Object> e = it.next();
      bu.append(e.getKey()).append("=").append(e.getValue()).append("\n");
    }
    Map<String, String> map = System.getenv();
    Iterator<Entry<String, String>> it2 = map.entrySet().iterator();
    while (it.hasNext())
    {
      Entry<String, String> e = it2.next();
      bu.append(e.getKey()).append("=").append(e.getValue()).append("\n");
    }
    System.out.println(bu.toString());
    return bu.toString();
  }
  
  /***
   * Print current classpath.
   */
  public final static String printClasspath()
  {
    String cp = System.getProperty("java.class.path");
    String[] ps = cp.split("\\;");
    List<String> l = new ArrayList<>();
    for (String s : ps)
    {
      int i = s.lastIndexOf("\\");
      String s1 = s.substring(i + 1);
      String s2 = s1 + " \t\t\t " + s;
      l.add(s2);
    }
    Collections.sort(l);
    StringBuilder bu = new StringBuilder();
    for (String s : l)
    {
      System.out.println(s);
      bu.append(s).append("\r\n");
    }
    return bu.toString();
  }
  
  /***
   * Byte array encode to base64.
   * 
   * @param byteArray
   * @return
   */
  public final static String encodeToBASE64(byte[] byteArray)
  {
    if (byteArray.length == 0)
      return "";
    return (new BASE64Encoder()).encodeBuffer(byteArray);
  }
  
  /**
   * Baset64 String decode to String.
   * 
   * @param s
   * @param charSet
   * @return
   */
  
  public final static String decodeFromBASE64(String s, String charSet)
  {
    String decodedStr;
    
    if (StringUtils.trimToEmpty(s).equals(""))
      return "";
      
    BASE64Decoder decoder = new BASE64Decoder();
    try
    {
      byte[] b = decoder.decodeBuffer(s);
      if (StringUtils.trimToEmpty(charSet).equals(""))
        decodedStr = new String(b);
      else
        decodedStr = new String(b, charSet);
        
      return decodedStr;
    }
    catch (Exception e)
    {
      LogUtil.getCoreLog().error("decodeFromBASE64 Error for the string: " + s + "with CharSet " + charSet, e);
      return "";
    }
  }
  
  public final static byte[] toBase64(String msg, String encoding)
  {
    byte[] msgBytes;
    try
    {
      msgBytes = msg.getBytes(encoding);
      sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
      sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
      String b64Str = enc.encodeBuffer(msgBytes);
      byte[] decodeBytes2 = dec.decodeBuffer(b64Str);
      return decodeBytes2;
    }
    catch (UnsupportedEncodingException e)
    {
      LogUtil.getCoreLog().error("", e);
    }
    catch (IOException e)
    {
      LogUtil.getCoreLog().error("", e);
    }
    return null;
  }
  
  public final static boolean isInterface(Class<?> c, String szInterface)
  {
    Class<?>[] face = c.getInterfaces();
    for (int i = 0, j = face.length; i < j; i++)
    {
      if (face[i].getName().equals(szInterface))
      {
        return true;
      }
      else
      {
        Class<?>[] face1 = face[i].getInterfaces();
        for (int x = 0; x < face1.length; x++)
        {
          if (face1[x].getName().equals(szInterface))
          {
            return true;
          }
          else if (isInterface(face1[x], szInterface))
          {
            return true;
          }
        }
      }
    }
    if (null != c.getSuperclass())
    {
      return isInterface(c.getSuperclass(), szInterface);
    }
    return false;
  }
  
  public final static boolean isAbstractFunction(Class<?> c)
  {
    int modifiers = c.getModifiers();
    return Modifier.isAbstract(modifiers);
  }
  
  public final static boolean isPublicFunction(Class<?> c)
  {
    int modifiers = c.getModifiers();
    return Modifier.isPublic(modifiers);
  }
  
  public final static Resource toResource(String file)
  {
    return toResource(Thread.currentThread().getContextClassLoader(), file);
  }
  
  public static Resource toResource(ClassLoader loader, String file)
  {
    InputStream in = loader.getResourceAsStream(file);
    byte[] bt;
    try
    {
      bt = new byte[in.available()];
      in.read(bt);
      in.close();
      ByteArrayResource res = new ByteArrayResource(bt);
      return res;
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
    return null;
  }
  
  public static void sleep(long n)
  {
    try
    {
      Thread.sleep(n);
    }
    catch (InterruptedException e)
    {
    }
  }
  
  public final static Object unsafeInvoke(Object inst, String name,Class[] classTypes, Object[] params)
  {
    try
    {
      Class<?>[] paramTypes = null;
      if (params != null)
      {
        paramTypes = new Class[params.length];
        for (int i = 0; i < params.length; i++)
        {
          paramTypes[i] = params[i].getClass();
        }
      }
      Method m = inst.getClass().getDeclaredMethod(name, classTypes);
      if (m.isAccessible())
        throw new Exception("Why u use this unsafe method to visit a safe method?");
      m.setAccessible(true);
      Object result = m.invoke(inst, params);
      m.setAccessible(false);
      return result;
    }
    catch (Exception e)
    {
      LogUtil.getCoreLog().warn("Unsafe invoke failure.Object={},name={}", inst, name, e);
    }
    return null;
  }
  
  public final static Object unsafeGet(Object inst, String name)
  {
    try
    {
      Field m = inst.getClass().getDeclaredField(name);
      if (m.isAccessible())
        throw new Exception("Why u use this unsafe method to visit a safe property?");
      m.setAccessible(true);
      Object result = m.get(inst);
      m.setAccessible(false);
      return result;
    }
    catch (Exception e)
    {
      LogUtil.getCoreLog().warn("Unsafe invoke failure.Object={},name={}", inst, name, e);
    }
    return null;
  }
  
  public final static void unsafeSet(Object inst, String name, Object value)
  {
    try
    {
      Field m = inst.getClass().getDeclaredField(name);
      if (m.isAccessible())
        throw new Exception("Why u use this unsafe method to visit a safe property?");
      m.setAccessible(true);
      m.set(inst, value);
      m.setAccessible(false);
    }
    catch (Exception e)
    {
      LogUtil.getCoreLog().warn("Unsafe invoke failure.Object={},name={}", inst, name, e);
    }
  }
  
  public static final int randomPort()
  {
    try
    {
      ServerSocket sso = new ServerSocket(0);
      int port = sso.getLocalPort();
      sso.close();
      return port;
    }
    catch (IOException e)
    {
    }
    return -1;
  }
  
  public final static boolean checkPortUsed(int port)
  {
    try
    {
      ServerSocket sso = new ServerSocket(port);
      sso.close();
    }
    catch (IOException e)
    {
      return true;
    }
    return false;
  }
  
  
  /**
   * parseCsv file and return a Map list for put all
   * @param filePath
   * @param regionPath
   * @param keyFields
   * @return
   * @throws IOException
   */
  public static Map<String,String> parseCsv(String filePath,String[] keyFields,char separator,char quotechar) throws IOException
  {
    Map<String,String> map = new HashMap<String,String>();
    File file = new File(filePath);
    if(!file.exists()){
     throw new IOException("file:"+filePath+" not exists!");
    }
    FileReader fReader = new FileReader(file);
    CSVReader csvReader = new CSVReader(fReader, separator, quotechar);
    String[] columns = csvReader.readNext();
    
    LinkedHashSet<String> columnSet = new LinkedHashSet<String>();
    for(int i=0;i<columns.length;i++)
    {
      columnSet.add(columns[i]);
    }
    
    //check if keyFields correct
    for(int i=0;i<keyFields.length;i++)
    {
        assertTrue(columnSet.contains(keyFields[i]));
    }
    
    String[] strs = null;
    do
    {
      strs = csvReader.readNext();
      //if no data ,continue to next line
      if(strs == null || strs.length <= 0)
      {
        continue;
      }
      //set values
      JsonObject value = JsonObject.create();
      Iterator<String> it = columnSet.iterator();
      int i = 0;
      while(it.hasNext())
      {
        value.append(it.next(),strs[i]);
        i++;
      }
      
      //set key
      JsonObject key = JsonObject.create();
      for(i=0;i<keyFields.length;i++)
      {
        String columnName = keyFields[i];
        key.append(columnName,StringUtils.trim(value.getValue(columnName)));
      }
      map.put(key.toJson(), value.toJson());
    }
    while (strs != null && strs.length > 0);
    csvReader.close();
    return map;
  }
}
